<?
/**
* @author Дубров Олег
* 
* Абстрактный класс записи в таблице
* По правилам моего движка, наследники tbl_absctract должны быть расположены в папке tbl и 
* Имя класса должно оформлять в виде tbl_<имя таблицы>.
* Только при соблюдении этих двух условий метод Database::fetchObject() сможет определить
* соответствующий результату SQL запроса класс и вернуть его экземпляр
*/

class tbl_abstract implements ArrayAccess{
  	protected $_sql_create;
	protected $_primary_key_field = 'id';
	private static $_schema;
	private $_types;
	private $_data = array();
	private $_data_modified ;
	protected $_db_name;
	protected $_table_name;
    static private $_chache = array();
	
	public function __construct($id = false, $table_name = false, $primary_key = false){
		if($table_name) $this->_table_name = $table_name;
        if($primary_key) $this->_primary_key_field = $primary_key;
		if(empty(self::$_schema)){ 
			self::$_schema = $this->parseCreateSql();
		}
		$this->createTypesFromSchema();
		if($id === false) return;
		if(!$this->load($id))
            throw new Exception("Не найден объект типа {$this->getTableName()} id='$id'");
	}
    
    public function getId(){
        return $this[$this->_primary_key_field];
    }
    
    public function getClass(){ return get_class($this); }
	
	public function parseCreateSql(){
        return false;
		preg_match_all("/`([^`]+)`\s+(int|integer|text|varchar|datetime|timestamp|double|decimal|enum)\s*(\([^\)]+\))?\s*(NOT\s+NULL)?\s*(DEFAULT\s+\S+)?\s*(auto_increment)?.*?,/i", $this->_sql_create, $matches);
		if(empty($matches)) return;
		$schema = null; 
		foreach($matches[0] as $key=>$one){
			$schema[$matches[1][$key]] = array(
					"Type"		=> $matches[2][$key],
					"Length"	=> $matches[3][$key],
					"Null"	=> empty($matches[4][$key])?"YES":"NO",
					"Default" => trim(preg_replace("/DEFAULT\s+/i","",$matches[5][$key])),
					"Extra" => $matches[6][$key],
			);
		}
		return $schema;
	}
    
    static public function getTablePrimaryKey($table_name){
        $key_fields = self::getTablePrimaryKeys($table_name);
        if(empty($key_fields)) return false;
        if(count($key_fields) > 1) return false;
        return array_pop($key_fields);
    }
    
    static public function getTablePrimaryKeys($table_name){
        $fields = Database::select("SHOW COLUMNS FROM `$table_name`", true);
        $key_fields = array();
        foreach($fields as $one)
            if($one['Key'] == 'PRI') $key_fields[] = $one['Field'];
        if(empty($key_fields)) return false;
        return $key_fields;
    }
    static public function getTableFirstField($table_name){
        $fields = Database::select("SHOW COLUMNS FROM `$table_name`", true);
        if(!count($fields)) return false;
        return $fields[0]['Field'];
    }
    static public function getTableFields($table_name){
        $fields = Database::select("SHOW COLUMNS FROM `$table_name`", true);
        $ret = array();
        foreach($fields as $one) $ret[] = $one['Field'];
        return $ret;
    }
	
	public function createTypesFromSchema(){
		if(!is_array(self::$_schema)) return;
		foreach(self::$_schema as $key=>$params){
			$class_name = 'tbl_type_'.strtolower($params['Type']);
			$this->_types[$key] = new $class_name($this, $key);
		}
	}
	
	public function dbupdate(){
		if(!$this->tableExists()){
			if(empty($this->_sql_create)){
				echo "Запрос создания для таблицы {$this->getTableName()} не задан";
				return;
			}
			Database::query($this->_sql_create);
			echo $this->_sql_create;
		}
		Database::query("SHOW FIELDS FROM {$this->getFullTableName()}");				
		$fields = Database::fetchAll();
		print_r($fields);
	}
	
	public function tableExists(){
		Database::query("show tables from ".Database::$database_name);
		$tables = Database::fetchAll();
		foreach($tables as $one)
			if(reset($one) == $this->getTableName()) return true;
		return false;
	}
	
	public function __set($varname, $value){
		$this[$varname] = $value;
	}
	
	public function __get($varname){
		return $this[$varname];
	}
	
	public function fieldIsset($field_name){
		return isset($this->_data[$field_name]);
	}
	
	public function getDataModified(){
		return $this->_data_modified;
	}
	
	public function isModified($field_name){
		return isset($this->_data_modified[$field_name]);
	}
	
	public function loadFromArray($arr){
        foreach($arr as $key=>$val)
            if(!isset($this->_data[$key]) || $this->_data[$key] != $val)
                $this->_data_modified[$key] = $val;
		$this->_data = array_merge($this->_data, $arr);
	}
	
	public function setData($arr){
		$this->_data = $arr;
	}	

	public function load($id){
        if(!empty(self::$_chache[get_class($this).$id])){
            $this->_data = self::$_chache[get_class($this).$id]->_data;
            return true;
        }
        self::$_chache[get_class($this).$id] = $this;
		return $this->loadByWhere("`{$this->_primary_key_field}`='$id'");
	}
	
	public function loadByWhere($where){
		$sql = "select * from {$this->getFullTableName()} where $where order by `{$this->_primary_key_field}` desc limit 1";
		Database::query($sql);
		$row = Database::fetchRow();
		if(!$row) return false;//throw new Exception("Не найден объект типа {$this->getTableName()} id='$id'");
		$this->_data = $row;
        return true;
	}
	
	public function offsetExists($offset){
		return array_key_exists($offset, $this->_data);
	}
	
	public function offsetGet($offset){
        if(!isset($this->_data[$offset])) return false;
		return $this->_data[$offset];
	}
	
	public function offsetSet($offset, $val){
		$this->_data[$offset] = $val;
		$this->_data_modified[$offset] = $val;
	}
	
	public function offsetUnset($offset){
		unset($this->_data[$offset]);
	}
	
	public function getTableName(){
		if($this->_table_name) return $this->_table_name;
		if(strpos(get_class($this), "_") === false)
			$table_name = substr(get_parent_class($this), strrpos(get_parent_class($this), "_")+1);
		else 
			$table_name = substr(get_class($this), strrpos(get_class($this), "_")+1);
		return $table_name;
	}
	
	public function getFullTableName(){
		if($this->_db_name)
			return "`{$this->_db_name}`.`{$this->getTableName()}`";
		else
			return "`{$this->getTableName()}`";
	}
	
	public function insert($ignore = false){
		$sql = "insert ".($ignore ? 'ignore ' : '')."into {$this->getFullTableName()} ";
		$pares = array();
        if(!empty($this->_data))
		    foreach($this->_data as $key=>$val) $pares[] = "`$key`='".mysql_real_escape_string($val)."'"; 
        else $pares[] = "`{$this->_primary_key_field}`=''";
		$sql.='set '.join(', ', $pares);
		Database::query($sql);
    	$this[$this->_primary_key_field] = Database::getInsertID();
    	if($this instanceof ILegoModel) Postman::riseInsertEvent($this);
	}

	public function replace(){
		$sql = "replace into {$this->getFullTableName()} ";
		$pares = array();
		foreach($this->_data as $key=>$val) $pares[] = "`$key`='".mysql_real_escape_string($val)."'"; 
		$sql.='set '.join(', ', $pares);
		Database::query($sql);
    	$this[$this->_primary_key_field] = Database::getInsertID();
    	if($this instanceof ILegoModel) Postman::riseInsertEvent($this);
	}
	
	public function update(){
		$sql = "update {$this->getFullTableName()} ";
		$pares = array();
		if(empty($this->_data_modified)) return;
		foreach($this->_data_modified as $key=>$val) $pares[] = "`$key`='".mysql_real_escape_string($val)."'"; 
		$sql.='set '.join(', ', $pares)." where `{$this->_primary_key_field}` = '{$this[$this->_primary_key_field]}'";
		Database::query($sql);
    	if($this instanceof ILegoModel) Postman::riseUpdateEvent($this);
	}	
	
	public function delete(){
		$sql = "delete from {$this->getFullTableName()} where `{$this->_primary_key_field}` = '{$this[$this->_primary_key_field]}' limit 1";
		Database::query($sql);
	}
    
    public function linkObject($obj){
        $table1 = min($this->getTableName(), $obj->getTableName());
        $table2 = max($this->getTableName(), $obj->getTableName());
        $value1 = $this->getTableName() == $table1 ? $this->getId() : $obj->getId();
        $value2 = $this->getTableName() == $table2 ? $this->getId() : $obj->getId();
        Database::query("replace into `_links` set table1='$table1', table2='$table2', value1='$value1', value2='$value2'");
    }    
    
    public function unlinkObject($obj){
        $table1 = min($this->getTableName(), $obj->getTableName());
        $table2 = max($this->getTableName(), $obj->getTableName());
        $value1 = $this->getTableName() == $table1 ? $this->getId() : $obj->getId();
        $value2 = $this->getTableName() == $table2 ? $this->getId() : $obj->getId();
        Database::query("delete from `_links` where table1='$table1' and table2='$table2' and value1='$value1' and value2='$value2'");
    }
    
    public function getLinkedObjects($class_name){
    	$obj = new $class_name;
    	if($this->getTableName() == $obj->getTableName()) throw new Exception("Одинаковые имена связанных таблиц '{$this->getTableName()}'");
    	if($this->getTableName() < $obj->getTableName())
    		$links = Database::select("select value2 as value from `_links` where table1='{$this->getTableName()}' and table2='{$obj->getTableName()}' and value1='{$this->getId()}'");
    	else
    		$links = Database::select("select value1 as value from `_links` where table1='{$obj->getTableName()}' and table2='{$this->getTableName()}' and value2='{$this->getId()}'");
    	$objects = array();
    	foreach($links as $row){
    		$objects[] = new $class_name($row['value']);
		}
		return $objects;
    }
    
    public function asArray(){
    	return $this->_data;
	}	
}
?>
